/*-------------------------------------------------------------------------
 *
 * pg_constraint.c
 *	  支持pg_constraint关系操作的常规例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/catalog/pg_constraint.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


 /*
 * CreateConstraintEntry
 *	创建一个约束表条目。
 *
 * 附属记录（例如实现
 * 约束的触发器或索引）*不会*在这里创建。但是我们确实
 * 使约束与其依赖的项之间建立依赖链接。
 *
 * 新约束的 OID 将被返回。
 */
Oid CreateConstraintEntry(const char *fc_constraintName,
					  Oid fc_constraintNamespace,
					  char fc_constraintType,
					  bool fc_isDeferrable,
					  bool fc_isDeferred,
					  bool fc_isValidated,
					  Oid fc_parentConstrId,
					  Oid fc_relId,
					  const int16 *fc_constraintKey,
					  int fc_constraintNKeys,
					  int fc_constraintNTotalKeys,
					  Oid fc_domainId,
					  Oid fc_indexRelId,
					  Oid fc_foreignRelId,
					  const int16 *fc_foreignKey,
					  const Oid *fc_pfEqOp,
					  const Oid *fc_ppEqOp,
					  const Oid *fc_ffEqOp,
					  int fc_foreignNKeys,
					  char fc_foreignUpdateType,
					  char fc_foreignDeleteType,
					  const int16 *fc_fkDeleteSetCols,
					  int fc_numFkDeleteSetCols,
					  char fc_foreignMatchType,
					  const Oid *fc_exclOp,
					  Node *fc_conExpr,
					  const char *fc_conBin,
					  bool fc_conIsLocal,
					  int fc_conInhCount,
					  bool fc_conNoInherit,
					  bool fc_is_internal)
{
	Relation	fc_conDesc;
	Oid			fc_conOid;
	HeapTuple	fc_tup;
	bool		fc_nulls[Natts_pg_constraint];
	Datum		fc_values[Natts_pg_constraint];
	ArrayType  *fc_conkeyArray;
	ArrayType  *fc_confkeyArray;
	ArrayType  *fc_conpfeqopArray;
	ArrayType  *fc_conppeqopArray;
	ArrayType  *fc_conffeqopArray;
	ArrayType  *fc_conexclopArray;
	ArrayType  *fc_confdelsetcolsArray;
	NameData	fc_cname;
	int			fc_i;
	ObjectAddress fc_conobject;
	ObjectAddresses *fc_addrs_auto;
	ObjectAddresses *fc_addrs_normal;

	fc_conDesc = table_open(ConstraintRelationId, RowExclusiveLock);

	Assert(fc_constraintName);
	namestrcpy(&fc_cname, fc_constraintName);

	 /*
	 * 将 C 数组转换为 Postgres 数组。
	 */
	if (fc_constraintNKeys > 0)
	{
		Datum	   *fc_conkey;

		fc_conkey = (Datum *) palloc(fc_constraintNKeys * sizeof(Datum));
		for (fc_i = 0; fc_i < fc_constraintNKeys; fc_i++)
			fc_conkey[fc_i] = Int16GetDatum(fc_constraintKey[fc_i]);
		fc_conkeyArray = construct_array(fc_conkey, fc_constraintNKeys,
									  INT2OID, 2, true, TYPALIGN_SHORT);
	}
	else
		fc_conkeyArray = NULL;

	if (fc_foreignNKeys > 0)
	{
		Datum	   *fc_fkdatums;

		fc_fkdatums = (Datum *) palloc(fc_foreignNKeys * sizeof(Datum));
		for (fc_i = 0; fc_i < fc_foreignNKeys; fc_i++)
			fc_fkdatums[fc_i] = Int16GetDatum(fc_foreignKey[fc_i]);
		fc_confkeyArray = construct_array(fc_fkdatums, fc_foreignNKeys,
									   INT2OID, 2, true, TYPALIGN_SHORT);
		for (fc_i = 0; fc_i < fc_foreignNKeys; fc_i++)
			fc_fkdatums[fc_i] = ObjectIdGetDatum(fc_pfEqOp[fc_i]);
		fc_conpfeqopArray = construct_array(fc_fkdatums, fc_foreignNKeys,
										 OIDOID, sizeof(Oid), true, TYPALIGN_INT);
		for (fc_i = 0; fc_i < fc_foreignNKeys; fc_i++)
			fc_fkdatums[fc_i] = ObjectIdGetDatum(fc_ppEqOp[fc_i]);
		fc_conppeqopArray = construct_array(fc_fkdatums, fc_foreignNKeys,
										 OIDOID, sizeof(Oid), true, TYPALIGN_INT);
		for (fc_i = 0; fc_i < fc_foreignNKeys; fc_i++)
			fc_fkdatums[fc_i] = ObjectIdGetDatum(fc_ffEqOp[fc_i]);
		fc_conffeqopArray = construct_array(fc_fkdatums, fc_foreignNKeys,
										 OIDOID, sizeof(Oid), true, TYPALIGN_INT);

		if (fc_numFkDeleteSetCols > 0)
		{
			for (fc_i = 0; fc_i < fc_numFkDeleteSetCols; fc_i++)
				fc_fkdatums[fc_i] = Int16GetDatum(fc_fkDeleteSetCols[fc_i]);
			fc_confdelsetcolsArray = construct_array(fc_fkdatums, fc_numFkDeleteSetCols,
												  INT2OID, 2, true, TYPALIGN_SHORT);
		}
		else
			fc_confdelsetcolsArray = NULL;
	}
	else
	{
		fc_confkeyArray = NULL;
		fc_conpfeqopArray = NULL;
		fc_conppeqopArray = NULL;
		fc_conffeqopArray = NULL;
		fc_confdelsetcolsArray = NULL;
	}

	if (fc_exclOp != NULL)
	{
		Datum	   *fc_opdatums;

		fc_opdatums = (Datum *) palloc(fc_constraintNKeys * sizeof(Datum));
		for (fc_i = 0; fc_i < fc_constraintNKeys; fc_i++)
			fc_opdatums[fc_i] = ObjectIdGetDatum(fc_exclOp[fc_i]);
		fc_conexclopArray = construct_array(fc_opdatums, fc_constraintNKeys,
										 OIDOID, sizeof(Oid), true, TYPALIGN_INT);
	}
	else
		fc_conexclopArray = NULL;

	/* 初始化 nulls 和值 */
	for (fc_i = 0; fc_i < Natts_pg_constraint; fc_i++)
	{
		fc_nulls[fc_i] = false;
		fc_values[fc_i] = (Datum) NULL;
	}

	fc_conOid = GetNewOidWithIndex(fc_conDesc, ConstraintOidIndexId,
								Anum_pg_constraint_oid);
	fc_values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(fc_conOid);
	fc_values[Anum_pg_constraint_conname - 1] = NameGetDatum(&fc_cname);
	fc_values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(fc_constraintNamespace);
	fc_values[Anum_pg_constraint_contype - 1] = CharGetDatum(fc_constraintType);
	fc_values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(fc_isDeferrable);
	fc_values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(fc_isDeferred);
	fc_values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(fc_isValidated);
	fc_values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(fc_relId);
	fc_values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(fc_domainId);
	fc_values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(fc_indexRelId);
	fc_values[Anum_pg_constraint_conparentid - 1] = ObjectIdGetDatum(fc_parentConstrId);
	fc_values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(fc_foreignRelId);
	fc_values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(fc_foreignUpdateType);
	fc_values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(fc_foreignDeleteType);
	fc_values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(fc_foreignMatchType);
	fc_values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(fc_conIsLocal);
	fc_values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(fc_conInhCount);
	fc_values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(fc_conNoInherit);

	if (fc_conkeyArray)
		fc_values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(fc_conkeyArray);
	else
		fc_nulls[Anum_pg_constraint_conkey - 1] = true;

	if (fc_confkeyArray)
		fc_values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(fc_confkeyArray);
	else
		fc_nulls[Anum_pg_constraint_confkey - 1] = true;

	if (fc_conpfeqopArray)
		fc_values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(fc_conpfeqopArray);
	else
		fc_nulls[Anum_pg_constraint_conpfeqop - 1] = true;

	if (fc_conppeqopArray)
		fc_values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(fc_conppeqopArray);
	else
		fc_nulls[Anum_pg_constraint_conppeqop - 1] = true;

	if (fc_conffeqopArray)
		fc_values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(fc_conffeqopArray);
	else
		fc_nulls[Anum_pg_constraint_conffeqop - 1] = true;

	if (fc_confdelsetcolsArray)
		fc_values[Anum_pg_constraint_confdelsetcols - 1] = PointerGetDatum(fc_confdelsetcolsArray);
	else
		fc_nulls[Anum_pg_constraint_confdelsetcols - 1] = true;

	if (fc_conexclopArray)
		fc_values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(fc_conexclopArray);
	else
		fc_nulls[Anum_pg_constraint_conexclop - 1] = true;

	if (fc_conBin)
		fc_values[Anum_pg_constraint_conbin - 1] = CStringGetTextDatum(fc_conBin);
	else
		fc_nulls[Anum_pg_constraint_conbin - 1] = true;

	fc_tup = heap_form_tuple(RelationGetDescr(fc_conDesc), fc_values, fc_nulls);

	CatalogTupleInsert(fc_conDesc, fc_tup);

	ObjectAddressSet(fc_conobject, ConstraintRelationId, fc_conOid);

	table_close(fc_conDesc, RowExclusiveLock);

	 /* 处理自动依赖集合 */
	fc_addrs_auto = new_object_addresses();

	if (OidIsValid(fc_relId))
	{
		 /*
		 * 注册约束到拥有关系的自动依赖，或者
		 * 如果提到特定列，则到具体列。
		 */
		ObjectAddress fc_relobject;

		if (fc_constraintNTotalKeys > 0)
		{
			for (fc_i = 0; fc_i < fc_constraintNTotalKeys; fc_i++)
			{
				ObjectAddressSubSet(fc_relobject, RelationRelationId, fc_relId,
									fc_constraintKey[fc_i]);
				add_exact_object_address(&fc_relobject, fc_addrs_auto);
			}
		}
		else
		{
			ObjectAddressSet(fc_relobject, RelationRelationId, fc_relId);
			add_exact_object_address(&fc_relobject, fc_addrs_auto);
		}
	}

	if (OidIsValid(fc_domainId))
	{
		 /*
		 * 注册约束到拥有域的自动依赖。
		 */
		ObjectAddress fc_domobject;

		ObjectAddressSet(fc_domobject, TypeRelationId, fc_domainId);
		add_exact_object_address(&fc_domobject, fc_addrs_auto);
	}

	record_object_address_dependencies(&fc_conobject, fc_addrs_auto,
									   DEPENDENCY_AUTO);
	free_object_addresses(fc_addrs_auto);

	 /* 处理普通依赖集合 */
	fc_addrs_normal = new_object_addresses();

	if (OidIsValid(fc_foreignRelId))
	{
		/*
		 * 注册约束与外部关系之间的正常依赖关系，或者如果提及了特定列，则与特定列之间的依赖关系。
		 */
		ObjectAddress fc_relobject;

		if (fc_foreignNKeys > 0)
		{
			for (fc_i = 0; fc_i < fc_foreignNKeys; fc_i++)
			{
				ObjectAddressSubSet(fc_relobject, RelationRelationId,
									fc_foreignRelId, fc_foreignKey[fc_i]);
				add_exact_object_address(&fc_relobject, fc_addrs_normal);
			}
		}
		else
		{
			ObjectAddressSet(fc_relobject, RelationRelationId, fc_foreignRelId);
			add_exact_object_address(&fc_relobject, fc_addrs_normal);
		}
	}

	if (OidIsValid(fc_indexRelId) && fc_constraintType == CONSTRAINT_FOREIGN)
	{
		/*
		 * 注册对支持外键约束的唯一索引的正常依赖关系。 （注意：对于与唯一或主键约束相关的索引，依赖关系的方向相反，并在此不做记录。）
		 */
		ObjectAddress fc_relobject;

		ObjectAddressSet(fc_relobject, RelationRelationId, fc_indexRelId);
		add_exact_object_address(&fc_relobject, fc_addrs_normal);
	}

	if (fc_foreignNKeys > 0)
	{
		/*
		 * 注册对支持外键约束的相等运算符的正常依赖关系。 如果PK和FK类型相同，则列的所有三个运算符都是相同的；否则，它们是不同的。
		 */
		ObjectAddress fc_oprobject;

		fc_oprobject.classId = OperatorRelationId;
		fc_oprobject.objectSubId = 0;

		for (fc_i = 0; fc_i < fc_foreignNKeys; fc_i++)
		{
			fc_oprobject.objectId = fc_pfEqOp[fc_i];
			add_exact_object_address(&fc_oprobject, fc_addrs_normal);
			if (fc_ppEqOp[fc_i] != fc_pfEqOp[fc_i])
			{
				fc_oprobject.objectId = fc_ppEqOp[fc_i];
				add_exact_object_address(&fc_oprobject, fc_addrs_normal);
			}
			if (fc_ffEqOp[fc_i] != fc_pfEqOp[fc_i])
			{
				fc_oprobject.objectId = fc_ffEqOp[fc_i];
				add_exact_object_address(&fc_oprobject, fc_addrs_normal);
			}
		}
	}

	record_object_address_dependencies(&fc_conobject, fc_addrs_normal,
									   DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs_normal);

	/*
	 * 我们不麻烦注册排除约束的排除运算符的依赖关系。 我们假设它们是支持索引的opclass的成员，因此通过该索引间接存在依赖关系。 （对于跨类型运算符来说，这可能相当棘手，但排除运算符永远不能是跨类型的。）
	 */

	if (fc_conExpr != NULL)
	{
		/*
		 * 注册约束与CHECK表达式中提到的对象之间的依赖关系。
		 */
		recordDependencyOnSingleRelExpr(&fc_conobject, fc_conExpr, fc_relId,
										DEPENDENCY_NORMAL,
										DEPENDENCY_NORMAL, false);
	}

	/* 新约束的创建后钩子 */
	InvokeObjectPostCreateHookArg(ConstraintRelationId, fc_conOid, 0,
								  fc_is_internal);

	return fc_conOid;
}

/*
 * 测试给定名称是否当前被用作给定对象（关系或域）的约束名称。
 *
 * 这用于决定是否接受用户指定的约束名称。 这一测试的目的在于允许其存在，除非对象上已存在相同的约束名称。
 *
 * 注意：调用者应该对给定对象持有独占锁，否则该测试可能会被并发添加所欺骗。
 */
bool ConstraintNameIsUsed(ConstraintCategory fc_conCat, Oid fc_objId,
					 const char *fc_conname)
{
	bool		fc_found;
	Relation	fc_conDesc;
	SysScanDesc fc_conscan;
	ScanKeyData fc_skey[3];

	fc_conDesc = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum((fc_conCat == CONSTRAINT_RELATION)
								 ? fc_objId : InvalidOid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum((fc_conCat == CONSTRAINT_DOMAIN)
								 ? fc_objId : InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_conname));

	fc_conscan = systable_beginscan(fc_conDesc, ConstraintRelidTypidNameIndexId,
								 true, NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	fc_found = (HeapTupleIsValid(systable_getnext(fc_conscan)));

	systable_endscan(fc_conscan);
	table_close(fc_conDesc, AccessShareLock);

	return fc_found;
}

/*
 * 在给定命名空间中，是否存在任何具有给定名称的约束？
 *
 * 这用于匹配ChooseConstraintName的规则，即我们应避免在命名空间内自动生成重复的约束名称。
 */
bool ConstraintNameExists(const char *fc_conname, Oid fc_namespaceid)
{
	bool		fc_found;
	Relation	fc_conDesc;
	SysScanDesc fc_conscan;
	ScanKeyData fc_skey[2];

	fc_conDesc = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_conname));

	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_connamespace,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_namespaceid));

	fc_conscan = systable_beginscan(fc_conDesc, ConstraintNameNspIndexId, true,
								 NULL, 2, fc_skey);

	fc_found = (HeapTupleIsValid(systable_getnext(fc_conscan)));

	systable_endscan(fc_conscan);
	table_close(fc_conDesc, AccessShareLock);

	return fc_found;
}

/*
 * 为新约束选择一个不冲突的名称。
 *
 * 这里的目标是选择在指定命名空间内唯一的名称。 Postgres并不要求这样，但SQL规范要求这样，并且一些应用依赖于它。 因此，我们避免选择会发生冲突的默认名称。
 *
 * name1、name2和label的用法与makeObjectName()相同，唯一的区别是label不能为NULL；如果需要，数字将附加到label以创建在指定命名空间内唯一的名称。
 *
 * 'others'可以是当前命令中已经选择的字符串名称的列表（但尚未反映到目录中）；我们也不会选择它们中的重复者。
 *
 * 注意：理论上，如果其他人同时选择相同名称，它仍可能发生冲突。 但在实践中，这种情况不太可能成为问题，尤其是在持有name1指定的关系的独占锁的情况下。
 *
 * 返回一个palloc的字符串。
 */
char * ChooseConstraintName(const char *fc_name1, const char *fc_name2,
					 const char *fc_label, Oid fc_namespaceid,
					 List *fc_others)
{
	int			fc_pass = 0;
	char	   *fc_conname = NULL;
	char		fc_modlabel[NAMEDATALEN];
	Relation	fc_conDesc;
	SysScanDesc fc_conscan;
	ScanKeyData fc_skey[2];
	bool		fc_found;
	ListCell   *fc_l;

	fc_conDesc = table_open(ConstraintRelationId, AccessShareLock);

	/* 首先尝试未修改的标签 */
	strlcpy(fc_modlabel, fc_label, sizeof(fc_modlabel));

	for (;;)
	{
		fc_conname = makeObjectName(fc_name1, fc_name2, fc_modlabel);

		fc_found = false;

		foreach(fc_l, fc_others)
		{
			if (strcmp((char *) lfirst(fc_l), fc_conname) == 0)
			{
				fc_found = true;
				break;
			}
		}

		if (!fc_found)
		{
			ScanKeyInit(&fc_skey[0],
						Anum_pg_constraint_conname,
						BTEqualStrategyNumber, F_NAMEEQ,
						CStringGetDatum(fc_conname));

			ScanKeyInit(&fc_skey[1],
						Anum_pg_constraint_connamespace,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(fc_namespaceid));

			fc_conscan = systable_beginscan(fc_conDesc, ConstraintNameNspIndexId, true,
										 NULL, 2, fc_skey);

			fc_found = (HeapTupleIsValid(systable_getnext(fc_conscan)));

			systable_endscan(fc_conscan);
		}

		if (!fc_found)
			break;

		/* 发现冲突，因此尝试一个新的名称组件 */
		pfree(fc_conname);
		snprintf(fc_modlabel, sizeof(fc_modlabel), "%s%d", fc_label, ++fc_pass);
	}

	table_close(fc_conDesc, AccessShareLock);

	return fc_conname;
}

/*
 * 删除单个约束记录。
 */
void RemoveConstraintById(Oid fc_conId)
{
	Relation	fc_conDesc;
	HeapTuple	fc_tup;
	Form_pg_constraint fc_con;

	fc_conDesc = table_open(ConstraintRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_conId));
	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for constraint %u", fc_conId);
	fc_con = (Form_pg_constraint) GETSTRUCT(fc_tup);

	/*
	 * 根据约束的目的进行特殊处理。
	 */
	if (OidIsValid(fc_con->conrelid))
	{
		Relation	fc_rel;

		/*
		 * 如果约束是针对一个关系，打开并对其进行独占锁定。
		 */
		fc_rel = table_open(fc_con->conrelid, AccessExclusiveLock);

		/*
		 * 如果正在删除的是检查约束，我们需要更新relchecks计数。 此更新将在我们提交时强制后端重建relcache条目。
		 */
		if (fc_con->contype == CONSTRAINT_CHECK)
		{
			Relation	fc_pgrel;
			HeapTuple	fc_relTup;
			Form_pg_class fc_classForm;

			fc_pgrel = table_open(RelationRelationId, RowExclusiveLock);
			fc_relTup = SearchSysCacheCopy1(RELOID,
										 ObjectIdGetDatum(fc_con->conrelid));
			if (!HeapTupleIsValid(fc_relTup))
				elog(ERROR, "cache lookup failed for relation %u",
					 fc_con->conrelid);
			fc_classForm = (Form_pg_class) GETSTRUCT(fc_relTup);

			if (fc_classForm->relchecks == 0)	/* 不应该发生 */
				elog(ERROR, "relation \"%s\" has relchecks = 0",
					 RelationGetRelationName(fc_rel));
			fc_classForm->relchecks--;

			CatalogTupleUpdate(fc_pgrel, &fc_relTup->t_self, fc_relTup);

			heap_freetuple(fc_relTup);

			table_close(fc_pgrel, RowExclusiveLock);
		}

		/* 保持对约束的rel的锁定直到事务结束 */
		table_close(fc_rel, NoLock);
	}
	else if (OidIsValid(fc_con->contypid))
	{
		/*
		 * XXX 暂时，在删除域约束时不执行任何特殊操作
		 *
		 * 可能应对域类型进行某种形式的锁定，但目前尚无此概念。
		 */
	}
	else
		elog(ERROR, "constraint %u is not of a known type", fc_conId);

	/* 销毁约束本身 */
	CatalogTupleDelete(fc_conDesc, &fc_tup->t_self);

	/* 清理 */
	ReleaseSysCache(fc_tup);
	table_close(fc_conDesc, RowExclusiveLock);
}

/*
 * RenameConstraintById
 *		重命名一个约束。
 *
 * 注意：这不是一个用户可见的函数；它不检查权限等。目前，它仅在重命名与约束相关的索引时调用，但它的设计比这更为通用，预计将来会有ALTER TABLE RENAME CONSTRAINT。
 */
void RenameConstraintById(Oid fc_conId, const char *fc_newname)
{
	Relation	fc_conDesc;
	HeapTuple	fc_tuple;
	Form_pg_constraint fc_con;

	fc_conDesc = table_open(ConstraintRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopy1(CONSTROID, ObjectIdGetDatum(fc_conId));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for constraint %u", fc_conId);
	fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

	/*
	 * 为了用户友好性，检查名称是否已经在使用中。
	 */
	if (OidIsValid(fc_con->conrelid) &&
		ConstraintNameIsUsed(CONSTRAINT_RELATION,
							 fc_con->conrelid,
							 fc_newname))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("constraint \"%s\" for relation \"%s\" already exists",
						fc_newname, get_rel_name(fc_con->conrelid))));
	if (OidIsValid(fc_con->contypid) &&
		ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
							 fc_con->contypid,
							 fc_newname))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("constraint \"%s\" for domain %s already exists",
						fc_newname, format_type_be(fc_con->contypid))));

	/* 好吧，进行重命名——元组是一个副本，所以可以随意修改 */
	namestrcpy(&(fc_con->conname), fc_newname);

	CatalogTupleUpdate(fc_conDesc, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(ConstraintRelationId, fc_conId, 0);

	heap_freetuple(fc_tuple);
	table_close(fc_conDesc, RowExclusiveLock);
}

/*
 * AlterConstraintNamespaces
 *		查找属于指定对象的任何约束，并将它们移动到指定的新命名空间。
 *
 * isType指示拥有对象是类型还是关系。
 */
void AlterConstraintNamespaces(Oid fc_ownerId, Oid fc_oldNspId,
						  Oid fc_newNspId, bool fc_isType, ObjectAddresses *fc_objsMoved)
{
	Relation	fc_conRel;
	ScanKeyData fc_key[2];
	SysScanDesc fc_scan;
	HeapTuple	fc_tup;

	fc_conRel = table_open(ConstraintRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_isType ? InvalidOid : fc_ownerId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_isType ? fc_ownerId : InvalidOid));

	fc_scan = systable_beginscan(fc_conRel, ConstraintRelidTypidNameIndexId, true,
							  NULL, 2, fc_key);

	while (HeapTupleIsValid((fc_tup = systable_getnext(fc_scan))))
	{
		Form_pg_constraint fc_conform = (Form_pg_constraint) GETSTRUCT(fc_tup);
		ObjectAddress fc_thisobj;

		ObjectAddressSet(fc_thisobj, ConstraintRelationId, fc_conform->oid);

		if (object_address_present(&fc_thisobj, fc_objsMoved))
			continue;

		/* 如果对象已经是命名空间的一部分，则不更新 */
		if (fc_conform->connamespace == fc_oldNspId && fc_oldNspId != fc_newNspId)
		{
			fc_tup = heap_copytuple(fc_tup);
			fc_conform = (Form_pg_constraint) GETSTRUCT(fc_tup);

			fc_conform->connamespace = fc_newNspId;

			CatalogTupleUpdate(fc_conRel, &fc_tup->t_self, fc_tup);

			/*
			 * 注意：目前，约束不会对命名空间拥有自己的依赖关系，因此我们不需要执行changeDependencyFor()。
			 */
		}

		InvokeObjectPostAlterHook(ConstraintRelationId, fc_thisobj.objectId, 0);

		add_exact_object_address(&fc_thisobj, fc_objsMoved);
	}

	systable_endscan(fc_scan);

	table_close(fc_conRel, RowExclusiveLock);
}


/*
 * ConstraintSetParentConstraint
 *		将分区的约束设置为其父约束的子约束，
 *		或者如果 parentConstrId 是 InvalidOid，则移除链接。
 *
 * 这会更新约束的 pg_constraint 行以显示它作为继承的，并
 * 添加 PARTITION 依赖以防止约束被单独删除。
 * 或者，反向操作。
 */
void ConstraintSetParentConstraint(Oid fc_childConstrId,
							  Oid fc_parentConstrId,
							  Oid fc_childTableId)
{
	Relation	fc_constrRel;
	Form_pg_constraint fc_constrForm;
	HeapTuple	fc_tuple,
				fc_newtup;
	ObjectAddress fc_depender;
	ObjectAddress fc_referenced;

	fc_constrRel = table_open(ConstraintRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_childConstrId));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for constraint %u", fc_childConstrId);
	fc_newtup = heap_copytuple(fc_tuple);
	fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_newtup);
	if (OidIsValid(fc_parentConstrId))
	{
		/* 不允许为已拥有父触发器的约束设置父触发器 */
		Assert(fc_constrForm->coninhcount == 0);
		if (fc_constrForm->conparentid != InvalidOid)
			elog(ERROR, "constraint %u already has a parent constraint",
				 fc_childConstrId);

		fc_constrForm->conislocal = false;
		fc_constrForm->coninhcount++;
		fc_constrForm->conparentid = fc_parentConstrId;

		CatalogTupleUpdate(fc_constrRel, &fc_tuple->t_self, fc_newtup);

		ObjectAddressSet(fc_depender, ConstraintRelationId, fc_childConstrId);

		ObjectAddressSet(fc_referenced, ConstraintRelationId, fc_parentConstrId);
		recordDependencyOn(&fc_depender, &fc_referenced, DEPENDENCY_PARTITION_PRI);

		ObjectAddressSet(fc_referenced, RelationRelationId, fc_childTableId);
		recordDependencyOn(&fc_depender, &fc_referenced, DEPENDENCY_PARTITION_SEC);
	}
	else
	{
		fc_constrForm->coninhcount--;
		fc_constrForm->conislocal = true;
		fc_constrForm->conparentid = InvalidOid;

		/* 确保没有进一步的继承。 */
		Assert(fc_constrForm->coninhcount == 0);

		CatalogTupleUpdate(fc_constrRel, &fc_tuple->t_self, fc_newtup);

		deleteDependencyRecordsForClass(ConstraintRelationId, fc_childConstrId,
										ConstraintRelationId,
										DEPENDENCY_PARTITION_PRI);
		deleteDependencyRecordsForClass(ConstraintRelationId, fc_childConstrId,
										RelationRelationId,
										DEPENDENCY_PARTITION_SEC);
	}

	ReleaseSysCache(fc_tuple);
	table_close(fc_constrRel, RowExclusiveLock);
}


/*
 * get_relation_constraint_oid
 *		在指定关系中找到具有指定名称的约束。
 *		返回约束的 OID。
 */
Oid get_relation_constraint_oid(Oid fc_relid, const char *fc_conname, bool fc_missing_ok)
{
	Relation	pg_constraint;
	HeapTuple	fc_tuple;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[3];
	Oid			fc_conOid = InvalidOid;

	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_conname));

	fc_scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true,
							  NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
		fc_conOid = ((Form_pg_constraint) GETSTRUCT(fc_tuple))->oid;

	systable_endscan(fc_scan);

	/* 如果没有这样的约束，则抱怨 */
	if (!OidIsValid(fc_conOid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
						fc_conname, get_rel_name(fc_relid))));

	table_close(pg_constraint, AccessShareLock);

	return fc_conOid;
}

/*
 * get_relation_constraint_attnos
 *		找到指定关系中具有指定名称的约束
 *		并返回受限列。
 *
 * 返回受限列的列 attnos 的 Bitmapset，attnos 通过 FirstLowInvalidHeapAttributeNumber 偏移，以便
 * 系统列可以被表示。
 *
 * *constraintOid 被设置为约束的 OID，失败时为 InvalidOid。
 */
Bitmapset * get_relation_constraint_attnos(Oid fc_relid, const char *fc_conname,
							   bool fc_missing_ok, Oid *fc_constraintOid)
{
	Bitmapset  *fc_conattnos = NULL;
	Relation	pg_constraint;
	HeapTuple	fc_tuple;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[3];

	/* 设置 *constraintOid，以避免对未初始化变量的抱怨 */
	*fc_constraintOid = InvalidOid;

	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_conname));

	fc_scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true,
							  NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
	{
		Datum		fc_adatum;
		bool		fc_isNull;

		*fc_constraintOid = ((Form_pg_constraint) GETSTRUCT(fc_tuple))->oid;

		/* 提取 conkey 数组，即受限列的 attnums */
		fc_adatum = heap_getattr(fc_tuple, Anum_pg_constraint_conkey,
							  RelationGetDescr(pg_constraint), &fc_isNull);
		if (!fc_isNull)
		{
			ArrayType  *fc_arr;
			int			fc_numcols;
			int16	   *fc_attnums;
			int			fc_i;

			fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
			fc_numcols = ARR_DIMS(fc_arr)[0];
			if (ARR_NDIM(fc_arr) != 1 ||
				fc_numcols < 0 ||
				ARR_HASNULL(fc_arr) ||
				ARR_ELEMTYPE(fc_arr) != INT2OID)
				elog(ERROR, "conkey is not a 1-D smallint array");
			fc_attnums = (int16 *) ARR_DATA_PTR(fc_arr);

			/* 构建结果值 */
			for (fc_i = 0; fc_i < fc_numcols; fc_i++)
			{
				fc_conattnos = bms_add_member(fc_conattnos,
										   fc_attnums[fc_i] - FirstLowInvalidHeapAttributeNumber);
			}
		}
	}

	systable_endscan(fc_scan);

	/* 如果没有这样的约束，则抱怨 */
	if (!OidIsValid(*fc_constraintOid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
						fc_conname, get_rel_name(fc_relid))));

	table_close(pg_constraint, AccessShareLock);

	return fc_conattnos;
}

/*
 * 返回给定关系中由给定索引强制执行的约束的 OID；如果没有这样的索引被编目，则返回 InvalidOid。
 *
 * 与 get_constraint_index 非常类似，此函数仅关心“拥有”给定索引的一个约束。
 * 因此，其他类型的约束（如唯一、主键和排除）将被忽略。
 */
Oid get_relation_idx_constraint_oid(Oid fc_relationId, Oid fc_indexId)
{
	Relation	pg_constraint;
	SysScanDesc fc_scan;
	ScanKeyData fc_key;
	HeapTuple	fc_tuple;
	Oid			fc_constraintId = InvalidOid;

	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_key,
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber,
				F_OIDEQ,
				ObjectIdGetDatum(fc_relationId));
	fc_scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId,
							  true, NULL, 1, &fc_key);
	while ((fc_tuple = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_constraint fc_constrForm;

		fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		/* 见上文 */
		if (fc_constrForm->contype != CONSTRAINT_PRIMARY &&
			fc_constrForm->contype != CONSTRAINT_UNIQUE &&
			fc_constrForm->contype != CONSTRAINT_EXCLUSION)
			continue;

		if (fc_constrForm->conindid == fc_indexId)
		{
			fc_constraintId = fc_constrForm->oid;
			break;
		}
	}
	systable_endscan(fc_scan);

	table_close(pg_constraint, AccessShareLock);
	return fc_constraintId;
}

/*
 * get_domain_constraint_oid
 *         查找具有指定名称的指定域上的约束。
 *         返回约束的 OID。
 */
Oid get_domain_constraint_oid(Oid fc_typid, const char *fc_conname, bool fc_missing_ok)
{
	Relation	pg_constraint;
	HeapTuple	fc_tuple;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[3];
	Oid			fc_conOid = InvalidOid;

	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_typid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_conname));

	fc_scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true,
							  NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
		fc_conOid = ((Form_pg_constraint) GETSTRUCT(fc_tuple))->oid;

	systable_endscan(fc_scan);

	/* 如果没有这样的约束，则抱怨 */
	if (!OidIsValid(fc_conOid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("constraint \"%s\" for domain %s does not exist",
						fc_conname, format_type_be(fc_typid))));

	table_close(pg_constraint, AccessShareLock);

	return fc_conOid;
}

/*
 * get_primary_key_attnos
 *         确定关系中主键的列，如果有的话。
 *
 * 返回主键列的列 attnos 的 Bitmapset，attnos 通过 FirstLowInvalidHeapAttributeNumber 偏移，以便可以表示系统列。
 *
 * 如果没有主键，则返回 NULL。如果主键约束是可延迟的，并且 deferrableOk 为 false，则我们也返回 NULL。
 *
 * *constraintOid 被设置为主键约束的 OID，失败时为 InvalidOid。
 */
Bitmapset * get_primary_key_attnos(Oid fc_relid, bool fc_deferrableOk, Oid *fc_constraintOid)
{
	Bitmapset  *fc_pkattnos = NULL;
	Relation	pg_constraint;
	HeapTuple	fc_tuple;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[1];

	/* 设置 *constraintOid，以避免对未初始化变量的抱怨 */
	*fc_constraintOid = InvalidOid;

	/* 扫描 pg_constraint 以查找目标关系的约束 */
	pg_constraint = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));

	fc_scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true,
							  NULL, 1, fc_skey);

	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
	{
		Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);
		Datum		fc_adatum;
		bool		fc_isNull;
		ArrayType  *fc_arr;
		int16	   *fc_attnums;
		int			fc_numkeys;
		int			fc_i;

		/* 跳过非主键的约束 */
		if (fc_con->contype != CONSTRAINT_PRIMARY)
			continue;

		/*
		 * 如果主键是可延迟的，但我们已被指示忽略可延迟约束，则我们不妨放弃搜索，因为每个表只能有一个主键。
 */
		if (fc_con->condeferrable && !fc_deferrableOk)
			break;

		/* 提取 conkey 数组，即主键列的 attnums */
		fc_adatum = heap_getattr(fc_tuple, Anum_pg_constraint_conkey,
							  RelationGetDescr(pg_constraint), &fc_isNull);
		if (fc_isNull)
			elog(ERROR, "null conkey for constraint %u",
				 ((Form_pg_constraint) GETSTRUCT(fc_tuple))->oid);
		fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
		fc_numkeys = ARR_DIMS(fc_arr)[0];
		if (ARR_NDIM(fc_arr) != 1 ||
			fc_numkeys < 0 ||
			ARR_HASNULL(fc_arr) ||
			ARR_ELEMTYPE(fc_arr) != INT2OID)
			elog(ERROR, "conkey is not a 1-D smallint array");
		fc_attnums = (int16 *) ARR_DATA_PTR(fc_arr);

		/* 构建结果值 */
		for (fc_i = 0; fc_i < fc_numkeys; fc_i++)
		{
			fc_pkattnos = bms_add_member(fc_pkattnos,
									  fc_attnums[fc_i] - FirstLowInvalidHeapAttributeNumber);
		}
		*fc_constraintOid = ((Form_pg_constraint) GETSTRUCT(fc_tuple))->oid;

		/* 无需进一步搜索 */
		break;
	}

	systable_endscan(fc_scan);

	table_close(pg_constraint, AccessShareLock);

	return fc_pkattnos;
}

/*
 * 从外键约束的 pg_constraint 元组中提取数据。
 *
 * 除第一个参数外，所有参数都是输出参数。如果调用者不需要其他输出参数（如 numfks、conkey 和 confkey），则可以将它们传递为 NULL。
 */
void DeconstructFkConstraintRow(HeapTuple fc_tuple, int *fc_numfks,
						   AttrNumber *fc_conkey, AttrNumber *fc_confkey,
						   Oid *fc_pf_eq_oprs, Oid *fc_pp_eq_oprs, Oid *fc_ff_eq_oprs,
						   int *fc_num_fk_del_set_cols, AttrNumber *fc_fk_del_set_cols)
{
	Oid			fc_constrId;
	Datum		fc_adatum;
	bool		fc_isNull;
	ArrayType  *fc_arr;
	int			fc_numkeys;

	fc_constrId = ((Form_pg_constraint) GETSTRUCT(fc_tuple))->oid;

	/*
	 * 我们期望数组是一维的正确类型数组；验证这一点。
	 * 我们不需要使用 deconstruct_array()，因为数组数据看起来只是一个 C 数组的值。
	 */
	fc_adatum = SysCacheGetAttr(CONSTROID, fc_tuple,
							 Anum_pg_constraint_conkey, &fc_isNull);
	if (fc_isNull)
		elog(ERROR, "null conkey for constraint %u", fc_constrId);
	fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
	if (ARR_NDIM(fc_arr) != 1 ||
		ARR_HASNULL(fc_arr) ||
		ARR_ELEMTYPE(fc_arr) != INT2OID)
		elog(ERROR, "conkey is not a 1-D smallint array");
	fc_numkeys = ARR_DIMS(fc_arr)[0];
	if (fc_numkeys <= 0 || fc_numkeys > INDEX_MAX_KEYS)
		elog(ERROR, "foreign key constraint cannot have %d columns", fc_numkeys);
	memcpy(fc_conkey, ARR_DATA_PTR(fc_arr), fc_numkeys * sizeof(int16));
	if ((Pointer) fc_arr != DatumGetPointer(fc_adatum))
		pfree(fc_arr);				/* 释放去烤的副本（如果有） */

	fc_adatum = SysCacheGetAttr(CONSTROID, fc_tuple,
							 Anum_pg_constraint_confkey, &fc_isNull);
	if (fc_isNull)
		elog(ERROR, "null confkey for constraint %u", fc_constrId);
	fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
	if (ARR_NDIM(fc_arr) != 1 ||
		ARR_DIMS(fc_arr)[0] != fc_numkeys ||
		ARR_HASNULL(fc_arr) ||
		ARR_ELEMTYPE(fc_arr) != INT2OID)
		elog(ERROR, "confkey is not a 1-D smallint array");
	memcpy(fc_confkey, ARR_DATA_PTR(fc_arr), fc_numkeys * sizeof(int16));
	if ((Pointer) fc_arr != DatumGetPointer(fc_adatum))
		pfree(fc_arr);				/* 释放去烤的副本（如果有） */

	if (fc_pf_eq_oprs)
	{
		fc_adatum = SysCacheGetAttr(CONSTROID, fc_tuple,
								 Anum_pg_constraint_conpfeqop, &fc_isNull);
		if (fc_isNull)
			elog(ERROR, "null conpfeqop for constraint %u", fc_constrId);
		fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
		/* 如果您更改以下测试，请参见 TryReuseForeignKey */
		if (ARR_NDIM(fc_arr) != 1 ||
			ARR_DIMS(fc_arr)[0] != fc_numkeys ||
			ARR_HASNULL(fc_arr) ||
			ARR_ELEMTYPE(fc_arr) != OIDOID)
			elog(ERROR, "conpfeqop is not a 1-D Oid array");
		memcpy(fc_pf_eq_oprs, ARR_DATA_PTR(fc_arr), fc_numkeys * sizeof(Oid));
		if ((Pointer) fc_arr != DatumGetPointer(fc_adatum))
			pfree(fc_arr);			/* 释放去烤的副本（如果有） */
	}

	if (fc_pp_eq_oprs)
	{
		fc_adatum = SysCacheGetAttr(CONSTROID, fc_tuple,
								 Anum_pg_constraint_conppeqop, &fc_isNull);
		if (fc_isNull)
			elog(ERROR, "null conppeqop for constraint %u", fc_constrId);
		fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
		if (ARR_NDIM(fc_arr) != 1 ||
			ARR_DIMS(fc_arr)[0] != fc_numkeys ||
			ARR_HASNULL(fc_arr) ||
			ARR_ELEMTYPE(fc_arr) != OIDOID)
			elog(ERROR, "conppeqop is not a 1-D Oid array");
		memcpy(fc_pp_eq_oprs, ARR_DATA_PTR(fc_arr), fc_numkeys * sizeof(Oid));
		if ((Pointer) fc_arr != DatumGetPointer(fc_adatum))
			pfree(fc_arr);			/* 释放去烤的副本（如果有） */
	}

	if (fc_ff_eq_oprs)
	{
		fc_adatum = SysCacheGetAttr(CONSTROID, fc_tuple,
								 Anum_pg_constraint_conffeqop, &fc_isNull);
		if (fc_isNull)
			elog(ERROR, "null conffeqop for constraint %u", fc_constrId);
		fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
		if (ARR_NDIM(fc_arr) != 1 ||
			ARR_DIMS(fc_arr)[0] != fc_numkeys ||
			ARR_HASNULL(fc_arr) ||
			ARR_ELEMTYPE(fc_arr) != OIDOID)
			elog(ERROR, "conffeqop is not a 1-D Oid array");
		memcpy(fc_ff_eq_oprs, ARR_DATA_PTR(fc_arr), fc_numkeys * sizeof(Oid));
		if ((Pointer) fc_arr != DatumGetPointer(fc_adatum))
			pfree(fc_arr);			/* 释放去烤的副本（如果有） */
	}

	if (fc_fk_del_set_cols)
	{
		fc_adatum = SysCacheGetAttr(CONSTROID, fc_tuple,
								 Anum_pg_constraint_confdelsetcols, &fc_isNull);
		if (fc_isNull)
		{
			*fc_num_fk_del_set_cols = 0;
		}
		else
		{
			int			fc_num_delete_cols;

			fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
			if (ARR_NDIM(fc_arr) != 1 ||
				ARR_HASNULL(fc_arr) ||
				ARR_ELEMTYPE(fc_arr) != INT2OID)
				elog(ERROR, "confdelsetcols is not a 1-D smallint array");
			fc_num_delete_cols = ARR_DIMS(fc_arr)[0];
			memcpy(fc_fk_del_set_cols, ARR_DATA_PTR(fc_arr), fc_num_delete_cols * sizeof(int16));
			if ((Pointer) fc_arr != DatumGetPointer(fc_adatum))
				pfree(fc_arr);		/* 释放去烤的副本（如果有） */

			*fc_num_fk_del_set_cols = fc_num_delete_cols;
		}
	}

	*fc_numfks = fc_numkeys;
}

/*
 * 确定关系是否可以证明在一组分组列上具有功能依赖关系。如果可以，则返回 true，并将证明所需的 pg_constraint OIDs 添加到 *constraintDeps 列表中。
 *
 * grouping_columns 是一组分组表达式，其中目标关系的列是具有指示的 varno/varlevelsup 的 Vars。
 *
 * 目前，我们只检查关系是否具有分组列的子集的主键。如果我们能够知道它们的所有列都不为空，我们也可以使用普通的唯一约束，但有一个问题：我们需要能够将非空性表示为添加到 *constraintDeps 的约束的一部分。FIXME 每当非空约束在 pg_constraint 中表示时。
 */
bool check_functional_grouping(Oid fc_relid,
						  Index fc_varno, Index fc_varlevelsup,
						  List *fc_grouping_columns,
						  List **fc_constraintDeps)
{
	Bitmapset  *fc_pkattnos;
	Bitmapset  *fc_groupbyattnos;
	Oid			fc_constraintOid;
	ListCell   *fc_gl;

	/* 如果关系没有主键，则无法证明功能依赖关系 */
	fc_pkattnos = get_primary_key_attnos(fc_relid, false, &fc_constraintOid);
	if (fc_pkattnos == NULL)
		return false;

	/* 确定所有出现在 grouping_columns 中的关系列 */
	fc_groupbyattnos = NULL;
	foreach(fc_gl, fc_grouping_columns)
	{
		Var		   *fc_gvar = (Var *) lfirst(fc_gl);

		if (IsA(fc_gvar, Var) &&
			fc_gvar->varno == fc_varno &&
			fc_gvar->varlevelsup == fc_varlevelsup)
			fc_groupbyattnos = bms_add_member(fc_groupbyattnos,
										   fc_gvar->varattno - FirstLowInvalidHeapAttributeNumber);
	}

	if (bms_is_subset(fc_pkattnos, fc_groupbyattnos))
	{
		/* 主键是分组列的子集，因此我们赢了 */
		*fc_constraintDeps = lappend_oid(*fc_constraintDeps, fc_constraintOid);
		return true;
	}

	return false;
}
